﻿#include "clientcallback.h"
#include <QApplication>
#include <QAudioInput>
#include <QCommandLineParser>
#include <QDir>
#include <QNetworkInterface>
#include <QPainter>
#include <QSysInfo>
#include <QUuid>
#include <libmedia.h>
#include "channelinfo.h"
#include "confsettings.h"
#include "globaltools.h"
#include "litedb.h"
#include "loginsemaphore.h"
#include "protohash.h"
#include "repository.h"
#include "servertimesync.h"
#include "videoeffect.h"
#include "usersettings.h"
#ifdef Q_OS_UNIX
#include <math.h>
#endif

//signed 16bit
static double calcVolume(const int sample_rate, const int channels, const unsigned char *data, unsigned int len)
{
    qreal level = 0.0;
    qreal sum = 0.0;
    const unsigned char *ptr = data;
    const unsigned char *const end = data + len;
    while (ptr < end) {
        const qint16 value = *reinterpret_cast<const qint16*>(ptr);
        const qreal fracValue = qreal(value) / (SHRT_MAX + 1);

        sum += fracValue * fracValue;
        ptr += 2;
    }
    const int numSamples = len / 2;
    level = qBound(qreal(0.0), sqrt(sum / numSamples), qreal(1.0));
    return level;
}

static inline bool is_device_test(quint64 rid, quint64 uid)
{
    return rid == 0 && uid == 0;
}

static inline void invoke(xos::uint32 invoke_type, ProtoHash &cmd)
{
    QByteArray buf = cmd.encode();
    librtc_invoke(invoke_type, (xos::uint8*)buf.data(), buf.size());
}

static const QMap<int, QString> error_map{
    {error_none, QObject::trECSUTF8("成功")},
    {error_code_password_mismatch, QObject::trECSUTF8("密码错误")},
    {error_code_keyword_blocked, QObject::trECSUTF8("关键字屏蔽")},
    {error_code_address_blocked, QObject::trECSUTF8("地址屏蔽")},
    {error_code_system_blocked, QObject::trECSUTF8("系统禁用")},
    {error_code_identify_blocked, QObject::trECSUTF8("ID禁用")},
    {error_code_blacklist_blocked, QObject::trECSUTF8("黑名单禁用")},
    {error_code_auth_failed, QObject::trECSUTF8("鉴权失败")},
    {error_code_unauthed, QObject::trECSUTF8("没有鉴权")},
    {error_code_permission_deny, QObject::trECSUTF8("权限错误")},
    {error_code_access_db_failed, QObject::trECSUTF8("数据库错误")},
    {error_code_invalid_uid, QObject::trECSUTF8("无效用户号")},
    {error_code_invalid_rid, QObject::trECSUTF8("无效频道号")},
    {error_code_invalid_version, QObject::trECSUTF8("版本错误")},
    {error_code_invalid_parameter, QObject::trECSUTF8("参数错误")},
    {error_code_limited_resource, QObject::trECSUTF8("资源耗尽")},
    {error_code_room_not_exist, QObject::trECSUTF8("频道不存在")},
    {error_code_user_not_exist, QObject::trECSUTF8("用户不存在")},
    {error_code_network_timeout, QObject::trECSUTF8("网络超时错误")},
    {error_code_invalid_mic_id, QObject::trECSUTF8("无效麦序号")},
    {error_code_invalid_token, QObject::trECSUTF8("token错误")},
    {error_code_capacity, QObject::trECSUTF8("超出容量")},
    {error_code_room_locked, QObject::trECSUTF8("频道上锁")},
    {error_code_destination_not_exist, QObject::trECSUTF8("目标不存在")},
    {error_code_referer_not_exist, QObject::trECSUTF8("参考对象不存在")},
    {error_code_special_privilege, QObject::trECSUTF8("特殊权限错误")},
    {error_code_alloc_mds, QObject::trECSUTF8("分配媒体服务器失败")},
    {error_code_only_admin, QObject::trECSUTF8("仅限管理员")},
    {error_code_member_enter, QObject::trECSUTF8("成员准入")},
    {error_code_system_forbid, QObject::trECSUTF8("系统封杀")},
    {error_code_forbid_guest, QObject::trECSUTF8("阻止游客")},
    {error_code_modify_mic_mode, QObject::trECSUTF8("修改麦模式错误")},
    {error_code_prevent_grant_mic, QObject::trECSUTF8("阻止抱麦")},
    {error_code_higher_micowner_right, QObject::trECSUTF8("需要更高权限")},
    {error_code_mic_disabled, QObject::trECSUTF8("窗口失效")},
    {error_code_crowd_exit, QObject::trECSUTF8("拥挤错误")},
    {error_code_system_dismiss, QObject::trECSUTF8("系统解散")},
    {error_code_owner_enter, QObject::trECSUTF8("仅限房主进入")},
    {error_code_proxy_capacity, QObject::trECSUTF8("超出proxy的容量")},
    {error_code_local_room_not_exist, QObject::trECSUTF8("终端并没有该频道信息")},
    {error_code_invalid_proxy, QObject::trECSUTF8("终端没有配置proxy")},
    {error_code_invalid_timestamp, QObject::trECSUTF8("时间戳错误")},
    {error_code_login_confilict, QObject::trECSUTF8("登录冲突")},
    {error_code_expired_account, QObject::trECSUTF8("账户过期")},
    {error_code_user_type_mismatch, QObject::trECSUTF8("用户类型错误")},
    {error_code_account_remote_stoped, QObject::trECSUTF8("账户远程停用")}
};

const QSize ClientCallback::cameraSize(320, 240);

ClientCallback::ClientCallback() : audioInput(Q_NULLPTR)
{
    QInstaller::Repository::registerMetaType();
    auto dataDir = QDir::toNativeSeparators(appDataDir() + '/').toStdString();

    librtc_param param;
    param.speaker_samplerate_ = 48000;
    param.speaker_channels_ = 2;
    param.microphone_samplerate_ = 48000;
    param.microphone_channels_ = 2;
    param.camera_width_ = cameraSize.width();
    param.camera_height_ = cameraSize.height();
    param.camera_fps_ = video_frame_rate_middle;
    param.video_render_type_ = video_pix_id_argb;
    param.device_type_ = login_device_win_pc;
    param.callback_ = (librtc_cb*)(this);
    param.hwnd_ = (void*)(popup.winId());
    param.data_path_ = dataDir;
    param.log_path_ = dataDir;
    librtc_init(param);
    set_video_list(QSet<qint64>());

    auto conf = ConfSettings::instance();
    mdsList = conf->mdsList.toUtf8();

    if(!conf->appDomain.isEmpty())
    {
        ProtoHash cmd;
        cmd.setParam(cmd_param_cmd_type, cmd_type_set_config);
        cmd.setParam(cmd_param_sub_cmd, QLatin1String("set_domain"));
        cmd.setParam(cmd_param_info, conf->appDomain);
        invoke(invoke_type_user_cmd, cmd);
    }

    if(!conf->validProxy.isEmpty())
        set_pxy_servers(conf->validProxy);

    if(!conf->protocolVer.isEmpty())
        set_protocol_version(conf->protocolVer);

    set_mds_list();

    auto settings = UserSettings::instance();
    bool ok = false;
    settings->getValue(UserSettings::sys_speaker_volume).toInt(&ok);
    if (!ok)
        settings->setValue(UserSettings::sys_speaker_volume, 10);
    settings->getValue(UserSettings::sys_micphone_volume).toInt(&ok);
    if (!ok)
        settings->setValue(UserSettings::sys_micphone_volume, 10);

    int cam_index = settings->getInt(UserSettings::sys_video_index);
    if (cam_index)
    {
        device_module_base* engine = device_module_base::get();
        const int max_device_cnt = 16;
        device_desc device_list[max_device_cnt];
        int device_cnt = engine->list_cameras(device_list, max_device_cnt);
        if(cam_index == virtualCameraId || cam_index < device_cnt)
        {
            ProtoHash cmd;
            cmd.setParam(cmd_param_cmd_type, cmd_type_set_camera_id);
            cmd.setParam(cmd_param_device_id, cam_index);
            invoke(invoke_type_media_cmd, cmd);
        }
        else if(settings->setValueIfChanged(UserSettings::sys_video_index, 0))
            settings->save_system_setting();
    }
    int mic_index = settings->getInt(UserSettings::sys_mic_index);
    if (mic_index)
    {
        ProtoHash cmd;
        cmd.setParam(cmd_param_cmd_type, cmd_type_set_microphone_id);
        cmd.setParam(cmd_param_device_id, mic_index);
        invoke(invoke_type_media_cmd, cmd);
    }
    setAudioInput(mic_index);

    int spk_index = settings->getInt(UserSettings::sys_speaker_index);
    if (spk_index)
    {
        ProtoHash cmd;
        cmd.setParam(cmd_param_cmd_type, cmd_type_set_speaker_id);
        cmd.setParam(cmd_param_device_id, spk_index);
        invoke(invoke_type_media_cmd, cmd);
    }
}

xos::int32 ClientCallback::ntfy_user_evnt(xos::uint8* buf, xos::uint32 len)
{
    if(QApplication::closingDown())
        return 0;
    QVariantHash hash = ProtoHash::decodeHash(QByteArray((const char*)buf, int(len)));
    if(hash.isEmpty())
        log_debug() << "decode failed:" << endl;
    else
    {
        int cmd_type = getParam(hash, cmd_param_cmd_type).toInt();
        if((cmd_type == cmd_type_login_im || (cmd_type == cmd_type_ack && getParam(hash, cmd_param_ack_cmd).toInt() == cmd_type_login_im)) && getParam(hash, cmd_param_code).toInt() == error_none)
        {
            auto semaphore = LoginSemaphore::instance();
            im_login_param.uid = getParam(hash, cmd_param_uid).toLongLong();//登陆成功，记录uid
            if(im_login_param.uid > 0)
                semaphore->tryAcquire(account_type_uid, QString::number(im_login_param.uid));
            const QString termimal_id = getParam(hash, cmd_param_terminal_id).toString();
            if(!termimal_id.isEmpty())
                semaphore->tryAcquire(account_type_terminal, termimal_id);
            const QString phone = getParam(hash, cmd_param_phone).toString();
            if(!phone.isEmpty())
                semaphore->tryAcquire(account_type_phone, phone);
            const QString imei = getParam(hash, cmd_param_imei).toString();
            if(!imei.isEmpty())
                semaphore->tryAcquire(account_type_imei, imei);
            const QString iccid = getParam(hash, cmd_param_iccid).toString();
            if(!iccid.isEmpty())
                semaphore->tryAcquire(account_type_iccid, iccid);
        }
        emit signal_user_evnt(cmd_type, hash);
    }
    return 0;
}

xos::int32 ClientCallback::ntfy_room_evnt(xos::uint8* buf, xos::uint32 len)
{
    if(QApplication::closingDown())
        return 0;
    QVariantHash hash = ProtoHash::decodeHash(QByteArray((const char*)buf, int(len)));
    if(hash.isEmpty())
        log_debug() << "decode failed:" << endl;
    else
    {
        emit signal_room_evnt(hash);
    }
    return 0;
}

xos::int32 ClientCallback::ntfy_local_audio(xos::int64 rid, xos::int64 uid, xos::uint8* audio,  xos::uint32  audio_len, const xos::int32  sample_rate, const xos::int32  channels)
{
    if(QApplication::closingDown())
        return 0;
    int volume = calcVolume(sample_rate, channels, audio, audio_len) * 100;
    if(is_device_test(rid, uid))
        emit signal_phone_test(volume);
    else
        emit signal_local_audio(rid, uid, volume);
    return 0;
}

xos::int32 ClientCallback::ntfy_remote_audio(xos::int64 rid, xos::int64 uid, xos::uint8* audio, xos::uint32  audio_len, const xos::int32  sample_rate, const xos::int32  channels)
{
    if(QApplication::closingDown())
        return 0;
    int volume = calcVolume(sample_rate, channels, audio, audio_len) * 100;
    if(is_device_test(rid, uid))
        emit signal_speaker_test(volume);
    else
        emit signal_remote_audio(rid, uid, volume);
    return 0;
}

static QImage libyuv_scale(const QImage& img, int width, int height)
{
    if(img.isNull())
        return img;
    QImage image = img;
    en_video_pix_id pid = video_pix_id_max;
    QImage::Format fmt = image.format();
    if(fmt == QImage::Format_ARGB32 || fmt == QImage::Format_RGB32)
        pid = video_pix_id_argb;
    else if(fmt == QImage::Format_RGB888)
        pid = video_pix_id_rgb24;
    else if(fmt == QImage::Format_RGBA8888)
        pid = video_pix_id_rgba;
    else
    {
        fmt = QImage::Format_RGB32;
        image = image.convertToFormat(fmt);
        pid = video_pix_id_argb;
    }
    int len = 0;
    if(pid != video_pix_id_max)
    {
        QImage buf(width, height, fmt);
        len = sws_convert_image(pid, image.width(), image.height(), const_cast<uchar*>(image.constBits()),
                                pid, width, height, buf.bits());
        if(len <= buf.byteCount())
            image = buf;
        else
            len = 0;
    }
    if(len <= 0)
        image = img.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    return image;
}

xos::int32 ClientCallback::ntfy_local_video(xos::int64 rid, xos::int64 uid, void* data, xos::int32  data_len, const xos::int32  width, const xos::int32  height, const xos::int32  pic_type)
{
    if(QApplication::closingDown())
        return 0;
    QPixmap pix;
    if(is_device_test(rid, uid))
    {
        QImage image = QImage((const uchar *)data,width, height, data_len / height, QImage::Format_RGB32).copy();
        if(!image.isNull())
            pix = QPixmap::fromImage(image);
        emit signal_video_test(pix);
        return 0;
    }
    //TODO:特效
    {
        auto videoEffect = VideoEffect::instance();
        QMutexLocker locker(&videoEffect->mutex);
        if(videoEffect->effFlag != VideoEffect::NoEffect)
        {
            QPixmap &captured = videoEffect->captureImage();
            int capWidth = width, capHeight = height;
            int inVideoWidth = width / 4, inVideoHeight = height / 4;
            if(videoEffect->pipFlag == VideoEffect::EffectInCamera)
            {
                capWidth = inVideoWidth;
                capHeight = inVideoHeight;
            }
            if(capWidth != captured.width() || capHeight!=captured.height())
            {
                QImage image = libyuv_scale(captured.toImage(), capWidth, capHeight);
                pix = QPixmap::fromImage(image);
            }
            else
                pix = captured;
            if(videoEffect->pipFlag != VideoEffect::NoPIP)
            {
                //画中画
                QImage camera = QImage((const uchar *)data,width, height, data_len / height, QImage::Format_RGB32).copy();
                if(videoEffect->pipFlag == VideoEffect::EffectInCamera)
                {
                    {
                        QPainter p(&camera);
                        QVector<QPoint> pointPairs(4);
                        p.setPen(Qt::darkGray);
                        pointPairs[0].rx() = pointPairs[2].rx() = pointPairs[3].rx() = width - capWidth - 1;
                        pointPairs[0].ry()  = pointPairs[1].ry() = pointPairs[2].ry() = height - capHeight - 1;
                        pointPairs[1].setX(width - 1);
                        pointPairs[3].setY(height - 1);
                        p.drawLines(pointPairs);
                        p.drawPixmap(width - capWidth, height - capHeight, pix);
                    }
                    pix = QPixmap::fromImage(camera);
                }
                else
                {
                    camera = libyuv_scale(camera, inVideoWidth, inVideoHeight);
                    QPainter p(&pix);
                    QVector<QPoint> pointPairs(4);
                    p.setPen(Qt::darkGray);
                    pointPairs[0].rx() = pointPairs[2].rx() = pointPairs[3].rx() = width - inVideoWidth - 1;
                    pointPairs[0].ry()  = pointPairs[1].ry() = pointPairs[2].ry() = height - inVideoHeight - 1;
                    pointPairs[1].setX(width - 1);
                    pointPairs[3].setY(height - 1);
                    p.drawLines(pointPairs);
                    p.drawImage(width - inVideoWidth, height - inVideoHeight, camera);
                }
            }
            QImage image = pix.toImage().convertToFormat(QImage::Format_RGB32);
            if(image.byteCount()!=data_len)
                return 0;
            memcpy(data,image.constBits(),data_len);
            emit signal_local_video(rid, uid, pix);
            return 1;
        }
    }
    QImage image = QImage((const uchar*)data, width, height, data_len / height, QImage::Format_RGB32).copy();
    if(image.isNull())
        return 0;
    pix = QPixmap::fromImage(image);
    emit signal_local_video(rid, uid, pix);
    return 0;
}

xos::int32 ClientCallback::ntfy_remote_video(xos::int64 rid, xos::int64 uid, void* data, xos::int32  data_len, const xos::int32  width, const xos::int32  height, const xos::int32  pic_type)
{
    if(QApplication::closingDown())
        return 0;
    QImage image((const uchar*)data, width, height, data_len / height, QImage::Format_RGB32);
    if(image.isNull())
        return 0;
    emit signal_remote_video(rid, uid, QPixmap::fromImage(image.copy()));
    return 0;
}

static QByteArray networkHardwareAddress()
{
    QStringList addnames;
    QList<QNetworkInterface> allInterfaces = QNetworkInterface::allInterfaces();
    foreach(const QNetworkInterface& inter, allInterfaces)
    {
        if(!inter.isValid() || inter.flags().testFlag(QNetworkInterface::IsLoopBack))
            continue;
        QString addr = inter.hardwareAddress();
        if(!addr.isEmpty() && !addnames.contains(addr) && addr.split(':',QString::SkipEmptyParts).size()==6)
            addnames << addr;
    }

    QUuid uuid = UserSettings::instance()->getValue(UserSettings::sys_uuid).toUuid();
    if (!uuid.isNull())
    {
        addnames.prepend(uuid.toByteArray().replace('{', "").replace('}', "").replace('-', QString()));
    }

    return addnames.join(';').toLatin1();
}

bool ClientCallback::im_login()
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_login_im);
    cmd.setParam(cmd_param_ver, 2);
    int account_type = account_type_uid;
    if(ConfSettings::instance()->defaultPhone)
        account_type = account_type_phone;
    else
    {
        qint64 toLL = im_login_param.account.toLongLong();
        if(toLL == 0)
            account_type = account_type_terminal;
        else{
            im_login_param.account = QString::number(toLL);
            if(im_login_param.account.size() == 11 && im_login_param.account[0] == '1')
                account_type = account_type_phone;
        }
    }

    if(!LoginSemaphore::instance()->tryAcquire(account_type, im_login_param.account))
    {
        return false;
    }

    cmd.setParam(cmd_param_account_type, account_type);
    cmd.setParam(cmd_param_account, im_login_param.account);
    cmd.setParam(cmd_param_password, im_login_param.hashed_password.toHex().constData());
    cmd.setParam(cmd_param_algorithm, crypt_algorithm_md5_once);
    invoke(invoke_type_user_cmd, cmd);
    return true;
}

bool ClientCallback::im_login(QString account, const QByteArray &hashedPW)
{
    im_login_param.uid = 0;
    im_login_param.account = account;
    im_login_param.hashed_password = hashedPW;
    return im_login();
}

void ClientCallback::im_logout()
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_logout);
    invoke(invoke_type_user_cmd, cmd);
    im_login_param = kkim_login_param();
}

QByteArray ClientCallback::im_send_msg(qint64 uid, const QString &name, int msg_type, const QString &text, qint64 time_len)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_send_msg);
    qint64 time = ServerTimeSync::instance()->getServerTime();
    QByteArray mid = LiteDb::create_mid(time, text);
    cmd.setParam(cmd_param_mid, mid);
    cmd.setParam(cmd_param_snd_id, get_my_id());
    if(!name.isEmpty())
        cmd.setParam(cmd_param_snd_name, name);
    cmd.setParam(cmd_param_rcv_id, uid);
    cmd.setParam(cmd_param_msg_type, msg_type);
    cmd.setParam(cmd_param_msg, text);
    cmd.setParam(cmd_param_msg_time, time);
    cmd.setParam(cmd_param_time_len, time_len);
    invoke(invoke_type_user_cmd, cmd);
    return mid;
}

void ClientCallback::im_delete_msg(qint64 uid)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_config);
    cmd.setParam(cmd_param_sub_cmd, "delete_user_msg");
    cmd.setParam(cmd_param_uid, uid);
    invoke(invoke_type_user_cmd, cmd);
}

void ClientCallback::im_mark_peer_read(qint64 uid, const QByteArray &mid)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_config);
    cmd.setParam(cmd_param_sub_cmd, "peer_read_msg");
    cmd.setParam(cmd_param_uid, uid);
    cmd.setParam(cmd_param_mid, mid);
    invoke(invoke_type_user_cmd, cmd);
}

void ClientCallback::im_mark_read(qint64 uid, const QByteArray &mid)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_config);
    cmd.setParam(cmd_param_sub_cmd, "read_user_msg");
    cmd.setParam(cmd_param_uid, uid);
    if(!mid.isEmpty())
        cmd.setParam(cmd_param_mid, mid);
    invoke(invoke_type_user_cmd, cmd);
}

QByteArray ClientCallback::channel_send_msg(qint64 rid, const QString &name, int msg_type, const QString &text, qint64 time_len)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_room_msg);
    cmd.setParam(cmd_param_rid, rid);
    cmd.setParam(cmd_param_uid, get_my_id());
    cmd.setParam(cmd_param_snd_id, get_my_id());
    //if(rcv_id)
    //    cmd[cmd_param_rcv_id] = std::to_string(rcv_id);
    if(!name.isEmpty())
        cmd.setParam(cmd_param_snd_name, name);
    qint64 time = ServerTimeSync::instance()->getServerTime();
    QByteArray mid = LiteDb::create_mid(time, text);
    cmd.setParam(cmd_param_mid, mid);
    cmd.setParam(cmd_param_msg_time, time);
    cmd.setParam(cmd_param_time_len, time_len);
    cmd.setParam(cmd_param_msg_type, msg_type);
    cmd.setParam(cmd_param_msg, text);
    invoke(invoke_type_room_cmd, cmd);
    return mid;
}

void ClientCallback::channel_send_msg(const QVariantHash &msg)
{
    ProtoHash cmd(msg);
    cmd.setParam(cmd_param_cmd_type, cmd_type_room_msg);
    invoke(invoke_type_room_cmd, cmd);
}

void ClientCallback::channel_delete_msg(qint64 rid)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_config);
    cmd.setParam(cmd_param_sub_cmd, "delete_room_msg");
    cmd.setParam(cmd_param_rid, rid);
    invoke(invoke_type_user_cmd, cmd);
}

void ClientCallback::channel_apply_ptt(qint64 rid, qint64 uid)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_apply_mic);
    cmd.setParam(cmd_param_type, apply_mic_type_audio);
    cmd.setParam(cmd_param_rid, rid);
    cmd.setParam(cmd_param_uid, uid);
    cmd.setParam(cmd_param_dest_id, uid);
    cmd.setParam(cmd_param_apply_ptt, 1);
    invoke(invoke_type_room_cmd, cmd);
}

void ClientCallback::channel_apply_ptt(qint64 rid)
{
    channel_apply_ptt(rid, get_my_id());
}

void ClientCallback::channel_apply_video(qint64 rid, qint64 uid)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_apply_mic);
    cmd.setParam(cmd_param_type, apply_mic_type_video);
    cmd.setParam(cmd_param_rid, rid);
    cmd.setParam(cmd_param_uid, uid);
    cmd.setParam(cmd_param_dest_id, uid);
    cmd.setParam(cmd_param_apply_ptt, 1);
    invoke(invoke_type_room_cmd, cmd);
}

void ClientCallback::channel_apply_video(qint64 rid)
{
    channel_apply_video(rid, get_my_id());
}

void ClientCallback::channel_free_mic(qint64 rid, qint64 uid)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_free_mic);
    cmd.setParam(cmd_param_rid, rid);
    cmd.setParam(cmd_param_dest_id, uid);
    invoke(invoke_type_room_cmd, cmd);
}

void ClientCallback::channel_free_mic(qint64 rid)
{
    channel_free_mic(rid, get_my_id());
}

void ClientCallback::channel_grant_mic(qint64 rid, qint64 uid, int mid)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_grant_mic);
    cmd.setParam(cmd_param_mic_id, mid);
    cmd.setParam(cmd_param_rid, rid);
    cmd.setParam(cmd_param_dest_id, uid);
    invoke(invoke_type_room_cmd, cmd);
}

void ClientCallback::channel_get_onlines(qint64 rid)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_get_room_onlines);
    cmd.setParam(cmd_param_rid, rid);
    //cmd.setParam(cmd_param_time_len, 0);
    invoke(invoke_type_room_cmd, cmd);
}

void ClientCallback::channel_mark_read(qint64 rid, const QByteArray &mid)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_config);
    cmd.setParam(cmd_param_sub_cmd, "read_room_msg");
    cmd.setParam(cmd_param_rid, rid);
    if(!mid.isEmpty())
        cmd.setParam(cmd_param_mid, mid);
    invoke(invoke_type_user_cmd, cmd);
}

void ClientCallback::set_video_list(const QSet<qint64> &rids)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_video_chans);
    QStringList arrayList;
    if(int size = rids.size())
    {
        QString array;
        auto it = rids.constBegin();
        for(int i = 0; i < size; i++,it++)
        {
            array = QString::number(*it);
            array.prepend("rid=");
        }
        arrayList.append(array);
    }
    cmd.setParam(cmd_param_video_room_list, arrayList.join(';'));
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::set_camera_id(int id)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_camera_id);
    cmd.setParam(cmd_param_device_id, id);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::start_camera_test()
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_start_camera_test);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::stop_camera_test()
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_stop_camera_test);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::set_speaker_id(int id)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_speaker_id);
    cmd.setParam(cmd_param_device_id, id);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::set_speaker_mute(qint64 rid, qint64 uid, bool mute)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_speaker_mute);
    cmd.setParam(cmd_param_mute, int(mute));
    cmd.setParam(cmd_param_rid, rid);
    cmd.setParam(cmd_param_uid, uid);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::set_speaker_volume(int value)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_speaker_volume);
    cmd.setParam(cmd_param_volume, value);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::start_speaker_test()
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_start_speaker_test);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::stop_speaker_test()
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_stop_speaker_test);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::set_microphone_id(int id)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_microphone_id);
    cmd.setParam(cmd_param_device_id, id);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::set_microphone_mute(qint64 rid, bool mute)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_microphone_mute);
    cmd.setParam(cmd_param_mute, int(mute));
    cmd.setParam(cmd_param_rid, rid);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::start_microphone_test()
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_start_microphone_test);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::stop_microphone_test()
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_stop_microphone_test);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::set_room_mute_list(const QByteArrayList &room_list)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_mute_chans);
    cmd.setParam(cmd_param_room_list, room_list.join(','));
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::set_user_mute_list(const QByteArrayList &user_list)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_mute_users);
    cmd.setParam(cmd_param_user_list, user_list.join(','));
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::set_p2p_user_list(const QByteArrayList &user_list) //格式:"uid=xx"
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_p2puser_list);
    cmd.setParam(cmd_param_p2puser_list, user_list.join(','));
    cmd.setParam(cmd_param_rid, P2PChanneInfo::p2pchannel_room_id);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::create_p2p_channel(qint64 uid, bool video)
{
    //setP2puserList
    set_p2p_user_list(QByteArrayList({"uid="+QByteArray::number(uid)}));

    //createChannel
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_create_channel);
    cmd.setParam(cmd_param_rid, P2PChanneInfo::p2pchannel_room_id);
    cmd.setParam(cmd_param_uid, get_my_id());
    cmd.setParam(cmd_param_audio_id, P2PChanneInfo::p2pchannel_audio_id);
    cmd.setParam(cmd_param_video_id, video ? P2PChanneInfo::p2pchannel_video_id : 0);
    cmd.setParam(cmd_param_private, 0);
    invoke(invoke_type_media_cmd, cmd);
}

void ClientCallback::free_p2p_channel()
{
    //deleteChannel
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_delete_channel);
    cmd.setParam(cmd_param_rid, P2PChanneInfo::p2pchannel_room_id);
    cmd.setParam(cmd_param_uid, get_my_id());
    invoke(invoke_type_media_cmd, cmd);

    //setP2puserList
    set_p2p_user_list(QByteArrayList());
}

void ClientCallback::set_mds_list()
{
    if(mdsList.isEmpty())
        return;
    QByteArray key = "set_uplayer_mds_list";
    librtc_cmdInvoke((xos::uint8*)key.data(), (xos::uint8*)mdsList.data());
}

const qint64 &ClientCallback::get_my_id() const
{
    return im_login_param.uid;
}

const string ClientCallback::get_my_id_stdstring() const
{
    if(im_login_param.uid > 0)
        return std::to_string(im_login_param.uid);
    return std::string();
}

const QString ClientCallback::get_my_id_QString() const
{
    if(im_login_param.uid > 0)
        return QString::number(im_login_param.uid);
    return QString();
}

const kkim_login_param &ClientCallback::get_login_param() const
{
    return im_login_param;
}

static const QString& getTestbedProxy()
{
    static const QString testbedProxy = ([](){
        QCommandLineParser parser;
        QCommandLineOption testbed("testbed", QString(), "proxyServer");
        parser.addOption(testbed);
        parser.process(QApplication::arguments());
        return parser.value(testbed);
    })();
    return testbedProxy;
}

void ClientCallback::set_pxy_servers(const QString &str)
{
    ProtoHash cmd;
    cmd.setParam(cmd_param_cmd_type, cmd_type_set_config);
    cmd.setParam(cmd_param_sub_cmd, QLatin1String("set_proxy"));
    cmd.setParam(cmd_param_info, getTestbedProxy().isEmpty() ? str : getTestbedProxy());
    invoke(invoke_type_user_cmd, cmd);
}

void ClientCallback::set_protocol_version(const QString &str)
{
    QByteArray key = "set_protocol_version";
    librtc_cmdInvoke((xos::uint8*)key.data(), (xos::uint8*)str.toLatin1().data());
}

QString ClientCallback::getErrorString(int code)
{
    return "("+QString::number(code)+")"+error_map.value(code);
}

void ClientCallback::setAudioInput(int idx)
{
    if(audioInput)
    {
        delete audioInput;
        audioInput = Q_NULLPTR;
    }
    if(idx == 0)
    {
        QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
        audioInput = new QAudioInput(info, QAudioFormat(), this);
    }
    if(idx > 0)
    {
        QList<QAudioDeviceInfo> devList = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
        if(idx <= devList.size())
        {
            QAudioDeviceInfo& info = devList[idx - 1];
            log_debug()<<"QAudioDeviceInfo : "<<qUtf8Printable(info.deviceName())<<endl;
            audioInput = new QAudioInput(info, QAudioFormat(), this);
        }
    }
}

void ClientCallback::setAudioInputVolume(int volume)
{
    if(audioInput && volume>=0 && volume<=100)
    {
        audioInput->setVolume(volume/100.0);
    }
}
